Action => Reducer => Selector => Subscribe
這是一個多麼自洽且美麗的一個流程,基本上無處安置第三者的存在
但假如我發出一個Action
之後,我有後續的動作要做呢?
我們雖然可以透過Reducer
的訂閱在去重複觸發新的Action
,但這就像是遂發槍一樣,打一發子彈出去,自己填入彈丸與火藥之後再打一發出去
不是說不行,那怕是現代也都還有中世紀全武裝格鬥大賽嘛
但就不太符合我們使用Rxjs追求資料流管理的目的了,我們為了未來可以管理方便,我們必須盡可能的減少巢狀Subscribe
的狀況
所以為了讓我們可以快速Action
以及後續相關的Action
做擊發,我們要使用Effect
來讓原本的單發遂發槍進化成半自動步槍啦
Effect
最為常見的環境是API的Request發起成功後續狀態,成功導入讀寫API成功的Action_Success
,失敗的則導入Action_Fail
的流程之中Effect
同樣需要知道是哪一Action
進行擊發的,總體而言架構與Reducer
接近,以下是範例程式碼:
import { Injectable, inject } from '@angular/core';
import { UserActions } from '../actions';
import { catchError, concatMap, map, of } from 'rxjs';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { UserInfoService } from 'src/app/shared/service/user-info.service';
import { UserLoginInfo, userLoginInfo } from '../../models';
@Injectable()
export class UserEffects {
private actions$ = inject(Actions);
private userInfoService = inject(UserInfoService);
/**
* 登入
*/
userLogin$ = createEffect(() => {
return this.actions$.pipe(
ofType(UserActions.USER_LOGIN),
concatMap((action) =>
this.userInfoService.login(action.userLogin).pipe(
map((result: UserLoginInfo) => {
if (result.isSuccess) {
return UserActions.USER_LOGIN_SUCCESS({
userLoginStatus: true,
userLoginDetail: result,
});
} else {
return UserActions.USER_LOGIN_FAIL({
userLoginStatus: false,
userLoginDetail: result,
});
}
}),
catchError(() =>
of(
UserActions.USER_LOGIN_FAIL({
userLoginStatus: false,
userLoginDetail: userLoginInfo,
})
)
)
)
)
);
});
}
首先,我們需要使用createEffect
來進行一個Effect
的userLogin$
實作,然後把它開始監聽它
監聽的位置一樣放在app.config.ts
之中
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './core/routes/app.routes';
import { provideStore } from '@ngrx/store';
import { reducer } from './core/store/reducers';
import { layoutReducerKey } from './core/store/reducers/layout.reducers';
import { effects } from './core/store/effects';
import { provideEffects } from '@ngrx/effects';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideStore({
[layoutReducerKey]: reducer[layoutReducerKey],
}),
provideEffects(effects),
],
};
假如這邊沒有effect在node_module中找不到的話,恩,很正常
為了不讓整個rxjs太肥,@ngrx/effects 也是先被切割出去的,所以下個安裝指令吧
npm i @ngrx/effects
這樣子我就可以正常將Effect
給啟用起來了
相信有朋友看到我用一個userInfoService
的Service來做使用,這個Service究竟是什麼呢?
Service主要是整個系統都可以進行引用的方法,來做整個系統方法的抽離與集中管理
而取得API與發起Action
等行為我們其實都可以在這個地方進行實作,這邊也是整個系統與頁務邏輯最為貼近的地方
包括且不限於外部API乃至於我們自己的layoutStatus都可以在這個地方進行引用
那我們這邊就開始嘗試一個API的串結吧
首先API的串接我們是需要一個測試用的API,我這邊也不太可能在教大家用Node.js或是python flask來簡單做一個測試用後端,那樣子30天的內容就太多了
所以我們這邊就用https://dummyapi.io/這個網頁服務來串接api
先申請一個帳號之後 我們就可以透過它的Doc來進行API的串接練習了~
先在shared的層級之中建立一個Service的資料夾並在下面分別建立兩個Service叫做user-info-management.service.ts與http-management.service.ts
前者用來進行user相關的全域管理,後者進行http的再格式化管理,後者的存在需要是因為我們可能因為需要打的API在不同的服務站台上,導致我們的Header需要另外做客製化
首先我們先來個基礎的GET
、POST
模板方便我們做不同的API客製化
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class HttpService {
private http = inject(HttpClient);
private get<T>(path: string, headers?: HttpHeaders, params?: any): Observable<T> {
return this.http.get<T>(path, {
params: params,
headers: headers,
});
}
private post<T>(
path: string,
headers: HttpHeaders,
params: any = null,
body: any,
responseType?: any
): Observable<T> {
return this.http.post<T>(path, body, {
params: params,
headers: headers,
responseType: responseType,
});
}
}
另外Http也是需要透過provideHttpClient
載入到app.config.js
,不然是沒有辦法開起一個對外的聯繫管道
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './core/routes/app.routes';
import { provideStore } from '@ngrx/store';
import { reducer } from './core/store/reducers';
import { layoutReducerKey } from './core/store/reducers/layout.reducers';
import { provideHttpClient } from '@angular/common/http';
import { effects } from './core/store/effects';
import { provideEffects } from '@ngrx/effects';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideStore({
[layoutReducerKey]: reducer[layoutReducerKey],
}),
provideHttpClient(),
provideEffects(effects),
],
};
接下來到了神奇的環節了,假如我們直接給一個localhost url進行api資料要求存取的話,就算後端跟我們在同一個站台(ip位址是相同的),也會因為js的實作是下載到客戶端上,你這時候宣告的localhost是取不到的,用戶的電腦以及手機平板並沒有啟用一個Serve並開啟一個接口叫做localhost:8080
這就是Proxy的功用所在,他可以代替我們進行轉值,將需求從**localhost:8080**
實際導致我們的同源站台127.156.1.233
上
{
"/originApi": {
"target": "https://localhost:8080",
"secure": false,
"changeOrigin": false,
"pathRewrite": {
"^/originApi": "api"
}
},
"/dummyApi": {
"target": "https://dummyapi.io/data/v1/",
"secure": true,
"changeOrigin": true,
"pathRewrite": {
"^/dummyApi": ""
}
}
}
secure主要是有沒有SSL憑證,而假如是非同源(not localhost)的話我們就可以用changeOrigin
為true來連外部API
而這個設定黨我們要放在angular.json
之中serve的option
進行系統初始化的載入,所以proxy一旦有變更必須要再重新ng serve
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "E_Shopping_System:build:production"
},
"development": {
"browserTarget": "E_Shopping_System:build:development"
}
},
"defaultConfiguration": "development",
"options": {
"port": 6837,
"browserTarget": "todo:build",
"proxyConfig": "src/proxy.config.json" //<--這裡
}
},
接下來回到我們的httpService,我自己的習慣是會RESTful API的方式重新命名外部來源的RESTful
所以長相如下
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { AppSettings } from 'src/app/core/settings/app.settings';
@Injectable({
providedIn: 'root',
})
export class HttpService {
private http = inject(HttpClient);
private get<T>(path: string, headers?: HttpHeaders, params?: any): Observable<T> {
return this.http.get<T>(path, {
params: params,
headers: headers,
});
}
getFormDummy<T>(path: string, params?: any): Observable<T> {
const headers = new HttpHeaders({ 'app-id': AppSettings.DUMMY_API_KEY });
return this.get(path, params, headers);
}
}
我原本想要將這個文章內容一口氣講完的,但發現光是說一下串接API就有點滿了,一時半會的說不完QQ,所以我們將會把後續的Action
與Effect
的實作放在明天講講